﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Reflection.Emit;
using System.Reflection;
using System.Xml;
using System.Globalization;

namespace HtmlExtractor
{

    delegate void GenericSetter(object target, object value);
    delegate object GenericGetter(object target);
    delegate object FastCreateInstanceHandler();
    delegate object FastInvokeHandler(object target, object[] parameters);
    /// <summary>
    /// 提供类型快速访问
    /// </summary>
    public static class ReflectorHelper
    {
        private static readonly Dictionary<string, FastInvokeHandler> methodCache = new Dictionary<string, FastInvokeHandler>();
        private static readonly Dictionary<Type, FastCreateInstanceHandler> typeCache = new Dictionary<Type, FastCreateInstanceHandler>();
        private static readonly Dictionary<string, object> propertyCache = new Dictionary<string, object>();
        public static object CreateInstance(string name)
        {
            Type t = Type.GetType(name);
            return CreateInstance(t);
        }
        /// <summary>
        /// 必须有默认构造函数
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        public static object CreateInstance(Type type)
        {
            
            if (!typeCache.ContainsKey(type))
            {
                typeCache[type] = GetInstanceCreator(type);
            }
            return ((FastCreateInstanceHandler)typeCache[type])();
        }
        public static object ChangeType2(Type type, object value)
        {
            if ((value == null) && type.IsGenericType)
            {
                return Activator.CreateInstance(type);
            }
            if (value == null)
            {
                return null;
            }
            if (type == value.GetType())
            {
                return value;
            }
            if (type.IsEnum)
            {
                if (value is string)
                {
                    return Enum.Parse(type, value as string, true);
                }
                return Enum.ToObject(type, value);
            }
            if (!type.IsInterface && type.IsGenericType)
            {
                Type type1 = type.GetGenericArguments()[0];
                object obj1 = ChangeType2(type1, value);
                return Activator.CreateInstance(type, new object[] { obj1 });
            }
            if ((value is string) && (type == typeof(Guid)))
            {
                return new Guid(value as string);
            }
            if ((value is string) && (type == typeof(Version)))
            {
                return new Version(value as string);
            }
            if (!(value is IConvertible))
            {
                return value;
            }
            return Convert.ChangeType(value, type, null);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj">调用方法的对象，静态方法为null</param>
        /// <param name="methodName"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static object CallMethod(object obj, string methodName, object[] parameters)
        {
            Type type = obj.GetType();
            string key = type.FullName + "$" + "method" + methodName;
            if (!methodCache.ContainsKey(key))
            {
                methodCache[key] = GetMethodInvoker(type, methodName);
            }
            return ((FastInvokeHandler)methodCache[key])(obj, parameters);
        }
        static FastInvokeHandler GetMethodInvoker(Type type, string methodName)
        {
            MethodInfo methodInfo = type.GetMethod(methodName);
            // generates a dynamic method to generate a FastInvokeHandler delegate
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) });

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

            ParameterInfo[] parameters = methodInfo.GetParameters();

            Type[] paramTypes = new Type[parameters.Length];

            // copies the parameter types to an array
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (parameters[i].ParameterType.IsByRef)
                    paramTypes[i] = parameters[i].ParameterType.GetElementType();
                else
                    paramTypes[i] = parameters[i].ParameterType;
            }

            LocalBuilder[] locals = new LocalBuilder[paramTypes.Length];

            // generates a local variable for each parameter
            for (int i = 0; i < paramTypes.Length; i++)
            {
                locals[i] = ilGenerator.DeclareLocal(paramTypes[i], true);
            }

            // creates code to copy the parameters to the local variables
            for (int i = 0; i < paramTypes.Length; i++)
            {
                ilGenerator.Emit(OpCodes.Ldarg_1);
                EmitFastInt(ilGenerator, i);
                ilGenerator.Emit(OpCodes.Ldelem_Ref);
                EmitCastToReference(ilGenerator, paramTypes[i]);
                ilGenerator.Emit(OpCodes.Stloc, locals[i]);
            }

            if (!methodInfo.IsStatic)
            {
                // loads the object into the stack
                ilGenerator.Emit(OpCodes.Ldarg_0);
                ilGenerator.Emit(OpCodes.Castclass, methodInfo.DeclaringType);
            }

            // loads the parameters copied to the local variables into the stack
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (parameters[i].ParameterType.IsByRef)
                    ilGenerator.Emit(OpCodes.Ldloca_S, locals[i]);
                else
                    ilGenerator.Emit(OpCodes.Ldloc, locals[i]);
            }

            // calls the method
            if (!methodInfo.IsStatic)
            {
                ilGenerator.EmitCall(OpCodes.Callvirt, methodInfo, null);
            }
            else
            {
                ilGenerator.EmitCall(OpCodes.Call, methodInfo, null);
            }

            // creates code for handling the return value
            if (methodInfo.ReturnType == typeof(void))
            {
                ilGenerator.Emit(OpCodes.Ldnull);
            }
            else
            {
                EmitBoxIfNeeded(ilGenerator, methodInfo.ReturnType);
            }

            // iterates through the parameters updating the parameters passed by ref
            for (int i = 0; i < paramTypes.Length; i++)
            {
                if (parameters[i].ParameterType.IsByRef)
                {
                    ilGenerator.Emit(OpCodes.Ldarg_1);
                    EmitFastInt(ilGenerator, i);
                    ilGenerator.Emit(OpCodes.Ldloc, locals[i]);
                    if (locals[i].LocalType.IsValueType)
                        ilGenerator.Emit(OpCodes.Box, locals[i].LocalType);
                    ilGenerator.Emit(OpCodes.Stelem_Ref);
                }
            }

            // returns the value to the caller
            ilGenerator.Emit(OpCodes.Ret);

            // converts the DynamicMethod to a FastInvokeHandler delegate to call to the method
            FastInvokeHandler invoker = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler));

            return invoker;
        }
        private static object ChangeType(object value, Type conversionType)
        {
            if (conversionType.IsGenericType &&
                conversionType.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
            {

                if (value == null)
                    return null;
                throw new NotSupportedException();
                //System.ComponentModel.NullableConverter nullableConverter
                //    = new System.ComponentModel.NullableConverter(conversionType);

                //conversionType = nullableConverter.UnderlyingType;
            }
            if (conversionType.GetInterface("IConvertible", true) != null)
            {
                return Convert.ChangeType(value, conversionType, null);
            }
            return value;
            //return Convert.ChangeType(value, conversionType,null);
        }

        public static void SetProperety(object obj, object value, string propertyName)
        {
            Type type = obj.GetType();
            string key = type.FullName + "$" + "set" + propertyName;
            Type ptype = type.GetProperty(propertyName).PropertyType;
            if (!propertyCache.ContainsKey(key))
            {
                propertyCache[key] = CreateSetMethod(type, propertyName);
            }
            object o= ChangeType2(ptype,value);           
            ((GenericSetter)propertyCache[key])(obj, o);
        }
       

        public static object GetProperty(object obj, string propertyName)
        {
            Type type = obj.GetType();
            string key = type.FullName + "$" + "get" + propertyName;
            if (!propertyCache.ContainsKey(key))
            {
                propertyCache[key] = CreateGetMethod(type, propertyName);
            }
            return ((GenericGetter)propertyCache[key])(obj);
        }
        static FastCreateInstanceHandler GetInstanceCreator(Type type)
        {
            // generates a dynamic method to generate a FastCreateInstanceHandler delegate
            DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, type, new Type[0]);

            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();

            // generates code to create a new object of the specified type using the default constructor
            ilGenerator.Emit(OpCodes.Newobj, type.GetConstructor(Type.EmptyTypes));

            // returns the value to the caller
            ilGenerator.Emit(OpCodes.Ret);

            // converts the DynamicMethod to a FastCreateInstanceHandler delegate to create the object
            FastCreateInstanceHandler creator = (FastCreateInstanceHandler)dynamicMethod.CreateDelegate(typeof(FastCreateInstanceHandler));

            return creator;
        }
        ///
        /// Creates a dynamic setter for the property
        ///
        private static GenericSetter CreateSetMethod(Type type, string propertyName)
        {
            PropertyInfo propertyInfo = type.GetProperty(propertyName);
            
            /*
            * If there's no setter return null
            */
            MethodInfo setMethod = propertyInfo.GetSetMethod();
            if (setMethod == null)
                return null;

            /*
            * Create the dynamic method
            */
            Type[] arguments = new Type[2];
            arguments[0] = arguments[1] = typeof(object);

            DynamicMethod setter = new DynamicMethod(
              String.Concat("_Set", propertyInfo.Name, "_"),
              typeof(void), arguments);
            ILGenerator generator = setter.GetILGenerator();
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            generator.Emit(OpCodes.Ldarg_1);

            if (propertyInfo.PropertyType.IsClass)
                generator.Emit(OpCodes.Castclass, propertyInfo.PropertyType);
            else
                generator.Emit(OpCodes.Unbox_Any, propertyInfo.PropertyType);

            generator.EmitCall(OpCodes.Callvirt, setMethod, null);
            generator.Emit(OpCodes.Ret);

            /*
            * Create the delegate and return it
            */
            return (GenericSetter)setter.CreateDelegate(typeof(GenericSetter));
        }

        ///
        /// Creates a dynamic getter for the property
        ///
        private static GenericGetter CreateGetMethod(Type type, string propertyName)
        {
            PropertyInfo propertyInfo = type.GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ArgumentNullException(propertyName);
            /*
            * If there's no getter return null
            */
            MethodInfo getMethod = propertyInfo.GetGetMethod();
            if (getMethod == null)
                return null;

            /*
            * Create the dynamic method
            */
            Type[] arguments = new Type[1];
            arguments[0] = typeof(object);

            DynamicMethod getter = new DynamicMethod(
              String.Concat("_Get", propertyInfo.Name, "_"),
              typeof(object), arguments);
            ILGenerator generator = getter.GetILGenerator();
            generator.DeclareLocal(typeof(object));
            generator.Emit(OpCodes.Ldarg_0);
            generator.Emit(OpCodes.Castclass, propertyInfo.DeclaringType);
            generator.EmitCall(OpCodes.Callvirt, getMethod, null);

            if (!propertyInfo.PropertyType.IsClass)
                generator.Emit(OpCodes.Box, propertyInfo.PropertyType);

            generator.Emit(OpCodes.Ret);

            /*
            * Create the delegate and return it
            */
            return (GenericGetter)getter.CreateDelegate(typeof(GenericGetter));
        }
        /// <summary>
        /// Gets the type of the typed collection's items.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The type of the typed collection's items.</returns>
        public static bool TryGetCollectionItemType(Type type,out Type itemType)
        {
            itemType = null;
            Type genericListType;
            if (type.IsArray)
            {
                itemType= type.GetElementType();
                return true;
            }
            else if (ImplementsGenericDefinition(type, typeof(IEnumerable<>), out genericListType))
            {
                if (genericListType.IsGenericTypeDefinition)
                    return false;
                else
                {
                    itemType= genericListType.GetGenericArguments()[0];
                    return true;
                }
            }
            else if (typeof(IEnumerable).IsAssignableFrom(type))
            {
                itemType = null;
                throw new Exception();
            }
            else
            {
                itemType = null;
                return false;
            }
        }

        public static bool ImplementsGenericDefinition(Type type, Type genericInterfaceDefinition, out Type implementingType)
        {
            //ValidationUtils.ArgumentNotNull(type, "type");
            //ValidationUtils.ArgumentNotNull(genericInterfaceDefinition, "genericInterfaceDefinition");

            if (!genericInterfaceDefinition.IsInterface || !genericInterfaceDefinition.IsGenericTypeDefinition)
                throw new ArgumentNullException();

            if (type.IsInterface)
            {
                if (type.IsGenericType)
                {
                    Type interfaceDefinition = type.GetGenericTypeDefinition();

                    if (genericInterfaceDefinition == interfaceDefinition)
                    {
                        implementingType = type;
                        return true;
                    }
                }
            }

            foreach (Type i in type.GetInterfaces())
            {
                if (i.IsGenericType)
                {
                    Type interfaceDefinition = i.GetGenericTypeDefinition();

                    if (genericInterfaceDefinition == interfaceDefinition)
                    {
                        implementingType = i;
                        return true;
                    }
                }
            }

            implementingType = null;
            return false;
        }
        /// <summary>Boxes a type if needed.</summary>
        /// <param name="ilGenerator">The MSIL generator.</param>
        /// <param name="type">The type.</param>
        private static void EmitBoxIfNeeded(ILGenerator ilGenerator, System.Type type)
        {
            if (type.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Box, type);
            }
        }
        /// <summary>Emits code to save an integer to the evaluation stack.</summary>
        /// <param name="ilGeneartor">The MSIL generator.</param>
        /// <param name="value">The value to push.</param>
        private static void EmitFastInt(ILGenerator ilGenerator, int value)
        {
            // for small integers, emit the proper opcode
            switch (value)
            {
                case -1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_M1);
                    return;
                case 0:
                    ilGenerator.Emit(OpCodes.Ldc_I4_0);
                    return;
                case 1:
                    ilGenerator.Emit(OpCodes.Ldc_I4_1);
                    return;
                case 2:
                    ilGenerator.Emit(OpCodes.Ldc_I4_2);
                    return;
                case 3:
                    ilGenerator.Emit(OpCodes.Ldc_I4_3);
                    return;
                case 4:
                    ilGenerator.Emit(OpCodes.Ldc_I4_4);
                    return;
                case 5:
                    ilGenerator.Emit(OpCodes.Ldc_I4_5);
                    return;
                case 6:
                    ilGenerator.Emit(OpCodes.Ldc_I4_6);
                    return;
                case 7:
                    ilGenerator.Emit(OpCodes.Ldc_I4_7);
                    return;
                case 8:
                    ilGenerator.Emit(OpCodes.Ldc_I4_8);
                    return;
            }

            // for bigger values emit the short or long opcode
            if (value > -129 && value < 128)
            {
                ilGenerator.Emit(OpCodes.Ldc_I4_S, (SByte)value);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Ldc_I4, value);
            }
        }
        /// <summary>Emits the cast to a reference, unboxing if needed.</summary>
        /// <param name="il">The MSIL generator.</param>
        /// <param name="type">The type to cast.</param>
        private static void EmitCastToReference(ILGenerator ilGenerator, System.Type type)
        {
            if (type.IsValueType)
            {
                ilGenerator.Emit(OpCodes.Unbox_Any, type);
            }
            else
            {
                ilGenerator.Emit(OpCodes.Castclass, type);
            }
        }
    }
}

